import * as THREE from "../../../modules/three/three.module.js";
import { WorldConfig } from '../lib/CommonConsts.js';
import WorkAreaMesh from '../lib/gadget/WorkAreaMesh.js';
import {
  box2Intersects,
  bufferAttributeToVectors,
  getPolygonGeometry,
  normalizeUvVectors,
} from '../lib/util/StaticMethods.js';
// import { BoolPolygonsUtil } from '../lib/util/bool-polygons-util.js';
import WorkArea from '../model/WorkArea.js';
import World3dEvent from './event/World3dEvent.js';
import UndoType from './undo/UndoType.js';
import ClipperTool from "../lib/clipper_js/ClipperTool.js";

/**
 * 仅用于初始化，如果工作区有孔洞，则不能调用此方法
 * @param {THREE.Mesh} mesh 工作区 或 不可喷涂区网格
 * @return {[{x: number, y: number}[], [孔索引]]} 仅用于初始化 WorkArea 的 verts属性
 * @see WorkArea
 */
export function getMeshVerts(mesh) {
  const areaData = mesh.vo;
  if (areaData.verts) {
    return areaData.verts;
  }
  const matInv = areaData.planeMatInv;
  const vectors = bufferAttributeToVectors(mesh.geometry.attributes.position);
  vectors.forEach((it) => it.applyMatrix4(matInv));
  const points = vectors.map((vector) => {
    return { x: vector.x, y: vector.y };
  });
  return [points, []];
}

/**
 * 获取工作区的包围矩形
 * @param {{x: number, y: number}[]} contour
 */
export function getMeshRect(contour) {
  const vectors = contour.map((it) => new THREE.Vector3(it.x, it.y, 0));
  const box3 = new THREE.Box3();
  box3.setFromPoints(vectors);
  return box3;
}

/**
 *
 * @param {THREE.Plane} plane  当前孔洞 或 工作区属于的几何平面
 * @param {WorkAreaMesh[] | HoleMesh[]} meshArr  每个元素必须有属性 vo
 * @param {THREE.Box3[]} boundingBoxes 当前孔洞 或 工作区的本地包围盒。这里用数组而不是单个元素的原因：当孔洞发生形变后，需要同时考虑形变前的包围盒子 与 形变后的包围盒子
 * @return {THREE.Mesh[]} 返回包围盒重叠的mesh, 并且可以认为这些mesh 都处于同一个几何平面
 */
export function getInvolvedMeshes(plane, meshArr, boundingBoxes) {
  const facing = plane.normal;
  const involvedMeshes = [];
  meshArr.forEach((mesh) => {
    const workArea = mesh.vo;
    const areaBox3 = workArea.localBox3;
    if (workArea.facing.manhattanDistanceTo(facing) < 0.0001) {
      const areaPlane = workArea.createPlane();
      const planeDistance = Math.abs(plane.constant - areaPlane.constant);
      if (planeDistance > WorldConfig.samePlaneTolerance) {
        // 两个工作区，它们的朝向虽然相同，但是它们所在几何平面的距离过大，也认为不是相关联的
        return;
      }
      if (boundingBoxes && boundingBoxes.length) {
        let intersectedBox = boundingBoxes.find((box) =>
          box2Intersects(box, areaBox3),
        );
        if (intersectedBox) {
          involvedMeshes.push(mesh);
        }
      } else {
        involvedMeshes.push(mesh);
      }
    }
  });
  return involvedMeshes;
}

/**
 * 使用老工作区网格 创建新网格，新网格和老网格必须在同一个几何平面，形状可以不同
 * @param vertObj 顶点列表 和 孔洞列表
 * @param {THREE.Mesh} oldWorkMesh 老工作区网格
 * @return {THREE.Mesh}
 */
function createAreaMesh(vertObj, oldWorkMesh) {
  const oldAreaData = oldWorkMesh.vo;
  const geometry = getPolygonGeometry(
    vertObj,
    oldAreaData.planeMat,
    oldAreaData.facing,
  );
  const meshNew = new WorkAreaMesh(geometry, oldWorkMesh.material.clone());
  const workArea = new WorkArea(oldAreaData.facing, oldAreaData.planeMat);
  workArea.localBox3 = getMeshRect(vertObj[0]);
  workArea.verts = vertObj;
  meshNew.vo = workArea;
  meshNew.renderOrder = WorldConfig.order_work_area;
  return meshNew;
}

// 工作区图形 减去 孔洞，先有不可喷涂区域，然后工作区覆盖在不可喷涂区域上
export function subtractAreaByHoles(
  holeMeshes,
  workAreaMesh,
  addingWorkArea = false,
) {
  const vertObj = getMeshVerts(workAreaMesh);
  const workArea = workAreaMesh.vo;
  workArea.verts = vertObj;
  workArea.localBox3 = getMeshRect(workArea.verts[0]);
  const box3 = workArea.localBox3;
  const facing = workArea.facing;
  const planeMat = workArea.planeMat;
  const involvedHoles = getInvolvedMeshes(workArea.createPlane(), holeMeshes, [
    box3,
  ]);

  if (!involvedHoles.length) {
    // 当前的工作区没有覆盖到已经存在的 不可喷涂区域
    if (addingWorkArea) {
      // 新增工作区时，没有覆盖到任何孔
      workAreaMesh.geometry.computeBoundingBox();
      workAreaMesh.geometry.computeBoundingSphere();
      const uvVectors = bufferAttributeToVectors(
        workAreaMesh.geometry.getAttribute('uv'),
      );
      const uvVectorsNormalized = normalizeUvVectors(uvVectors);
      workAreaMesh.geometry.setAttribute(
        'uv',
        new THREE.BufferAttribute(new Float32Array(uvVectorsNormalized), 2),
      );
      return { removed: [], added: [] };
    } else {
      // 孔（不可喷涂区域）删除后，可能会走到这里，工作区从有一个孔，变为没有孔
      const geometry = getPolygonGeometry(vertObj, planeMat, facing);
      const meshNew = new WorkAreaMesh(geometry, workAreaMesh.material.clone());
      const workArea0 = new WorkArea(facing, planeMat);
      workArea0.localBox3 = getMeshRect(vertObj[0]);
      workArea0.verts = vertObj;
      meshNew.areaObj = workArea0;
      meshNew.renderOrder = WorldConfig.order_work_area;
      return { removed: [workAreaMesh], added: [meshNew] };
    }
  }

  let added = [vertObj];
  involvedHoles.forEach((mesh) => {
    const holeObj = getMeshVerts(mesh);
    const newAdded = [];
    added.forEach((areaObj) => {
      // const result = BoolPolygonsUtil.polygonAsubB(areaObj, holeObj);
      const result = ClipperTool.polygonAsubB(areaObj, holeObj);
      newAdded.push(...result);
    });
    added = newAdded;
  });
  const newMeshes = added.map((obj) => createAreaMesh(obj, workAreaMesh));
  return { removed: [workAreaMesh], added: newMeshes };
}

// 两个 2d 图形之间做布尔差集运算，先有工作区图形，然后不可喷涂区域覆盖在工作区上
export function subtractHoleToArea(workAreaMeshes, holeMesh) {
  const hole = getMeshVerts(holeMesh);
  const holeArea = holeMesh.vo;
  const holeBox3 = holeArea.localBox3;
  const holeFacing = holeArea.facing;
  const facingPlaneMat = holeArea.planeMat;
  const involvedMeshes = getInvolvedMeshes(
    holeArea.createPlane(),
    workAreaMeshes,
    [holeBox3],
  );
  const uninvolved = workAreaMeshes.filter(
    (it) => !involvedMeshes.includes(it),
  );

  const remained = [];
  const added = [];
  const removed = [];
  const vertsArr = [];
  involvedMeshes.forEach((mesh) => {
    const vertObj = getMeshVerts(mesh);
    // 这里认为holeArea.verts 与 vertObj 中的坐标，都是二维坐标，并且属于同一个坐标系
    // const result = BoolPolygonsUtil.polygonAsubB(vertObj, hole);
    const result = ClipperTool.polygonAsubB(vertObj, hole);
    vertsArr.push(...result);
    if (result.length) {
      result.forEach((obj) => {
        const geometry = getPolygonGeometry(obj, facingPlaneMat, holeFacing);
        const meshNew = new WorkAreaMesh(geometry, mesh.material.clone());
        const workArea = new WorkArea(holeFacing, facingPlaneMat);
        workArea.localBox3 = getMeshRect(obj[0]);
        workArea.verts = obj;
        workArea.computeArea();
        workArea.initWidthAndHeight();
        meshNew.vo = workArea;
        meshNew.renderOrder = WorldConfig.order_work_area;
        added.push(meshNew);
      });
      removed.push(mesh);
    } else {
      const workArea = mesh.vo;
      workArea.verts = vertObj;
      workArea.localBox3 = getMeshRect(vertObj[0]);
      remained.push(mesh);
    }
  });
  const arr = remained.concat(added);
  workAreaMeshes.length = 0;
  workAreaMeshes.push(...uninvolved);
  workAreaMeshes.push(...arr);
  return { removed, added };
}

// 工作区编辑，负责新增和删除
export default class EditWorkArea {
  world3d;

  constructor(world3d) {
    this.world3d = world3d;
  }

  // 修改工作区名称
  changeSelectedAreaName(name) {
    const areaMesh = this.world3d.store.pickingArea();
    if (areaMesh) {
      const areaData = areaMesh.vo;
      areaData.name = name;
    }
  }

  removeAreaMesh() {
    const store = this.world3d.store;
    const pickedAreaMesh = store.pickingArea();
    if (!pickedAreaMesh) {
      return;
    }
    if (pickedAreaMesh === store.usingWorkArea) {
      store.usingWorkArea = null
    }
    this.world3d.scene.dispatchEvent(World3dEvent.clearPaneSelection);
    const ctx = {
      added: [pickedAreaMesh],
      removed: [],
      arr: store.areaMeshes,
      scene: pickedAreaMesh.parent,
    };
    this.world3d.editRecord.addAction(UndoType.removeWorkArea, ctx);

    const index = store.areaMeshes.indexOf(pickedAreaMesh);
    store.areaMeshes.splice(index, 1);
    pickedAreaMesh.removeFromParent();
    store.resetPicking(null);
    this.world3d.editorContentChanged = true;
  }

  /**
   * 新增新的工作区，支持新工作区 与多个老工作区之间的覆盖；发生覆盖时，新工作区与同色的老工作区合并，不同颜色的老工作区减去新工作区
   */
  addNewWorkArea() {
    const store = this.world3d.store;
    const workAreaMesh = store.addingAreaMesh;
    const workArea = workAreaMesh.vo;
    if (store.usingPlaneRef) {
      const vectors = bufferAttributeToVectors(workAreaMesh.geometry.attributes.position);
      workArea.toOtherSpace(store.getFacing().planeMat0, store.getFacing().planeMatInv0, vectors)
    } else {
      // vertObj 中的二维坐标 位于workAreaMesh所在几何平面的本地坐标系
      workArea.verts = getMeshVerts(workAreaMesh);
      workArea.updateLocalBox()
    }
    const vertObj = workArea.verts;
    workArea.initWidthAndHeight();
    const areaPlane = workArea.createPlane();
    let involvedMeshes = getInvolvedMeshes(areaPlane, store.areaMeshes, [
      workArea.localBox3,
    ]);
    involvedMeshes = involvedMeshes.filter((it) =>
      // BoolPolygonsUtil.isPolygonAintersectsB(vertObj, it.vo.verts),
      ClipperTool.isPolygonAintersectsB(vertObj, it.vo.verts),
    );
    const meshRemoved = []; // 只保存 store.areaMeshes 可以找到的元素
    const meshAdded = [];
    let workAreaOverlapped; // true：当前新增工作区和孔洞重叠 或者 和同种颜色的老工作区重叠
    // console.log('addNewWorkArea, b ')
    const curColor = workAreaMesh.material.color.getHexString();
    const sameColorMeshes = [];
    const diffColorMeshes = [];
    involvedMeshes.forEach((mesh) => {
      const color = mesh.material.color.getHexString();
      if (curColor === color) {
        sameColorMeshes.push(mesh);
      } else {
        diffColorMeshes.push(mesh);
      }
    });
    workAreaOverlapped = sameColorMeshes.length > 0;
    // b. 新工作区可能和其他工作区重叠了
    // b.1. 新工作区 和 若干个重叠工作区 不同色，各个老工作区减去新工作区
    diffColorMeshes.forEach((mesh) => {
      const areaVertObj = getMeshVerts(mesh);
      const objBak = [areaVertObj[0].slice(), areaVertObj[1].slice()];
      // const result = BoolPolygonsUtil.polygonAsubB(areaVertObj, vertObj);
      const result = ClipperTool.polygonAsubB(areaVertObj, vertObj);
      if (ClipperTool.equalPolygons(result[0], objBak)) {
        return;
      }
      meshRemoved.push(mesh);
      result.forEach((obj) => {
        const meshNew = createAreaMesh(obj, mesh);
        meshAdded.push(meshNew);
      });
    });
    // b.2. 新工作区 和 若干个重叠工作区 是同一种颜色，需要合并图形
    if (sameColorMeshes.length) {
      const polygons = [vertObj];
      sameColorMeshes.forEach((mesh) => {
        const workArea = mesh.vo;
        // workArea.resetShapeToBoundingRect()
        polygons.push(workArea.verts);
      });
      // 先 把各个工作区作为矩形 合并为一个图形
      // const result = BoolPolygonsUtil.mergePolygons(polygons);
      const result = ClipperTool.polygonsMerge(polygons);
      if (result.length === 1) {
        // 然后 合并的图形减去相关的孔洞，得到若干个新的工作区
        const mergedObj = result[0];
        const box3 = new THREE.Box3();
        mergedObj[0].forEach((it) =>
          box3.expandByPoint(new THREE.Vector3(it.x, it.y, 0)),
        );
        const involvedHoles = getInvolvedMeshes(areaPlane, store.holeMeshes, [
          box3,
        ]);
        let addedObjs = [mergedObj];
        involvedHoles.forEach((mesh) => {
          const holeObj = getMeshVerts(mesh);
          const newAdded = [];
          addedObjs.forEach((areaObj) => {
            // const result = BoolPolygonsUtil.polygonAsubB(areaObj, holeObj);
            const result = ClipperTool.polygonAsubB(areaObj, holeObj);
            newAdded.push(...result);
          });
          addedObjs = newAdded;
        });
        addedObjs.forEach((vertObj) => {
          meshAdded.push(createAreaMesh(vertObj, workAreaMesh));
        });
      } else {
        workAreaOverlapped = false;
        console.error('新增工作区时，合并同色工作区出错：', result);
      }
      meshRemoved.push(...sameColorMeshes);
    }

    if (!workAreaOverlapped) {
      // a. 新工作区没有和其他工作区重叠，有可能与孔洞重叠
      const { removed, added } = subtractAreaByHoles(
        store.holeMeshes,
        workAreaMesh,
        true,
      );
      console.log('addNewWorkArea, a, ', removed, added);
      meshAdded.push(...added);
      if (added.length) {
        // 新工作区和孔洞重叠
        workAreaOverlapped = true;
      }
    }
    // 善后工作：维护 areaMeshes 数组，mesh移入 移出场景
    meshRemoved.forEach((mesh) => {
      const index = store.areaMeshes.indexOf(mesh);
      store.areaMeshes.splice(index, 1);
      mesh.removeFromParent();
    });
    meshAdded.forEach((mesh) => {
      if (!store.areaMeshes.includes(mesh)) {
        store.areaMeshes.push(mesh);
      }
      this.world3d.scene.add(mesh);
    });
    if (workAreaOverlapped) {
      store.clearAddingAreaMesh();
    } else {
      store.addAreaMeshToStore();
      meshAdded.push(workAreaMesh);
    }
    // 新增工作区 引起的上下文变动 保存到 用户操作栈
    const ctx = {
      added: meshAdded,
      removed: meshRemoved,
      arr: store.areaMeshes,
      scene: this.world3d.scene,
    };
    this.world3d.editRecord.addAction(UndoType.addWorkArea, ctx);
    return ctx
  }

  // 鼠标按下的回调, 返回true表示，鼠标按下事件，被消耗掉，不传递给其他模块
  pointerDown(event) {
    if (event.button === 2) {
      // mouse right button down
      return false;
    }
    const store = this.world3d.store;
    if (store.stateAddingArea && (store.pickingTile() || store.pickingArea() || store.usingPlaneRef)) {
      store.initNewAreaMesh();
    }
    return false;
  }

  pointerMove(event) {
    if (event.button === 2) {
      // mouse right button down
      return;
    }
    const store = this.world3d.store;
    const areaMesh = store.addingAreaMesh;
    if (!store.stateAddingArea || !areaMesh) {
      return;
    }
    store.updateAddingAreaRect();
  }

  pointerUpUnmoved(event) {
    const store = this.world3d.store;
    if (event.button === 2) {
      // mouse right button down
      if (store.stateAddingArea) {
        store.freeState();
        this.world3d.switchCamera(false);
      }
      return;
    }
    store.clearAddingAreaMesh();
  }

  pointerUpMoved(event) {
    if (event.button === 2) {
      // mouse right button down
      return;
    }
    const store = this.world3d.store;
    if (store.stateAddingArea && store.addingAreaMesh) {
      // 新增工作区时，画矩形完毕
      const workArea = store.addingAreaMesh.vo;
      if (workArea.area < 0.01) {
        store.clearAddingAreaMesh();
      } else {
        this.addNewWorkArea();
      }
    }
  }
}
